package net.skyaims.lori.context;

import net.skyaims.lori.core.Provider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.*;

/**
 * Created by wujianing on 14-5-18.
 */
public abstract class ClassPathContext extends ClassLoaderContext implements Context {

    private static final Logger log = LoggerFactory.getLogger(ClassPathContext.class);

    private static final String FILE_SCHEMA = "file";

    private static final String[] IGNORE_PACKAGES = new String[]{
            "net.skyaims.lori.context",
            "net.skyaims.lori.core"
    };

    private Map<String, Resource> resources = new HashMap<String, Resource>();

    public ClassPathContext() {
        super();
        init();
    }

    public void init() {
        loadClassPath();
        loadResource();
    }

    private void loadClassPath() {
        Set<URL> urls = new HashSet<URL>();
        try {
            Enumeration<URL> itor = getClassLoader().getResources("");
            for (URL url; itor.hasMoreElements(); ) {
                url = itor.nextElement();
                urls.add(url);
            }
        } catch (IOException e) {
            log.error(e.getMessage(), e);
        }
        scanClassPath(urls);
    }

    private void scanClassPath(Set<URL> urls) {

        try {
            for (URL url : urls) {
                try {
                    URI uri = url.toURI();
                    log.info("scan classpath : {}", uri.getSchemeSpecificPart());
                    File file = new File(url.toURI());
                    if (FILE_SCHEMA.equals(file.toURI().getScheme())) {
                        scanResource(file, file.toURI().getSchemeSpecificPart());
                    }
                } catch (URISyntaxException e) {
                    log.error(e.getMessage(), e);
                }
            }
        } catch (IOException e) {
            log.error("context classload get resources exception");
            log.error(e.getMessage(), e);
        }

    }

    private void scanResource(File root, String baseClassPath) throws IOException {

        if (root.isDirectory()) {
            File[] files = root.listFiles();
            for (File file : files) {
                scanResource(file, baseClassPath);
            }
        } else {
            if (root.getName().contains(".class")) {
                Resource resource = new Resource();
                resource.setBaseClassPath(baseClassPath);
                resource.setProviderFilePath(root.toURI().getSchemeSpecificPart());
                resource.setProviderClassName(convertRelativeClassPath(baseClassPath, root.toURI().getSchemeSpecificPart()));
                resource.setProviderFileRefrence(root);
                resources.put(resource.getProviderClassName(), resource);
            }
        }

    }

    private void loadResource() {

        Set<String> classNames = resources.keySet();

        List<String> removeKeys = new ArrayList<String>();

        for (String className : classNames) {

            log.info("loadResource : {}", className);

            boolean isIgnore = false;

            for (String ignore : IGNORE_PACKAGES) {
                if (className.contains(ignore)) {
                    isIgnore = true;
                    continue;
                }
            }

            if (isIgnore) {
                continue;
            }

            Resource resource = resources.get(className);
            Class<?> clz = null;
            byte[] classBuff = null;
            boolean isReadFile = false;
            try {
                clz = loadClass(resource.getProviderClassName());
            } catch (ClassNotFoundException e) {
                isReadFile = true;
            }
            try {
                classBuff = FileUtils.readToBytes(resource.getProviderFileRefrence());
            } catch (IOException e) {
                log.error(e.getMessage(), e);
            }
            if (isReadFile) {
                try {
                    clz = defineClassForBytes(resource.getProviderClassName(), classBuff);
                } catch (Exception e) {
                    log.error(e.getMessage(), e);
                }
            }
            if (clz != null && clz.getAnnotation(Provider.class) != null) {
                resource.setProviderFileBytes(classBuff);
                resource.setProviderClassRefrence(clz);
                resource.setProviderInterfaceRefrences(clz.getInterfaces());
                log.info(resource.toString());
            } else {
                removeKeys.add(className);
            }
        }

        for (String key : removeKeys) {
            resources.remove(key);
        }

    }

    private String convertRelativeClassPath(String baseClassPath, String absoluteClassPath) {
        String relativeClassPath = absoluteClassPath.replace(baseClassPath, "");
        return relativeClassPath.replace('/', '.');
    }

    protected Map<String, Resource> getResources() {
        return resources;
    }

}
